#include "DataPCH.h"
#include "DataManager.h"
#include "AutoGen_DataBlock.h"

namespace data
{
	void Manager::Initialise(const ManagerConfig& config)
	{
		slots.resize(config.slotConfigs.size());

		for (size_t i = 0; i < config.slotConfigs.size(); ++i)
		{
			slots[i].config = config.slotConfigs[i];
			slots[i].memory.resize(slots[i].config.sizeInBytes);
			slots[i].pOwner = 0;
		}

		dataBlocks.resize(config.dataBlockConfigs.size());

		for (size_t i = 0; i < config.dataBlockConfigs.size(); ++i)
		{
			dataBlocks[i].config = config.dataBlockConfigs[i];
			dataBlocks[i].pSlot = 0;
			dataBlocks[i].config.fileSizeInBytes = pf::FileIO::FileGetSize(dataBlocks[i].config.strFilename);
		}

		currentRequest.state = Request::EState_Idle;
		enableHotload = true;
	}

	void Manager::Shutdown()
	{
		bool somethingLeft = false;
		for (std::vector<Slot>::iterator it = slots.begin(), end = slots.end(); it != end; ++it)
		{
			if (it->handlers.size() > 0)
			{
				CR_OUTPUT_STRING2("[data] Slot '%s' still has %d allocated slot(s)!", it->config.strType.c_str(), it->handlers.size());
				somethingLeft = true;
			}
		}
		if (somethingLeft)
		{
			//CR_ERROR_STRING("[data] Data not correctly released for shutdown!");
		}
	}

	bool Manager::Step()
	{
		if (currentRequest.state == Request::EState_Idle && requests.size())
		{
			currentRequest = requests.front();
			requests.pop_front();
		}

		switch (currentRequest.state)
		{
		case Request::EState_Idle:
			{
				break;
			}
		case Request::EState_Register_CheckingForLoadedData:
			{
				if (currentRequest.hDataBlockHandle->pSlot)
				{
					currentRequest.hDataBlockHandle->pSlot->handlers.push_back(currentRequest.pHandler);
					currentRequest.state = Request::EState_Register_StartRegistering;
				}
				else
				{
					currentRequest.state = Request::EState_Register_AquiringSlot;
				}
				break;
			}
		case Request::EState_Register_AquiringSlot:
			{
				currentRequest.hDataBlockHandle->pSlot = 0;
				for (std::vector<Slot>::iterator it = slots.begin(), end = slots.end(); it != end; ++it)
				{
					if (it->pOwner == 0 && it->config.strType == currentRequest.hDataBlockHandle->config.strSlotType)
					{
						currentRequest.hDataBlockHandle->pSlot = &*it;
						break;
					}
				}

				if (currentRequest.hDataBlockHandle->pSlot)
				{
					currentRequest.hDataBlockHandle->pSlot->pOwner = currentRequest.hDataBlockHandle;
					currentRequest.hDataBlockHandle->pSlot->handlers.push_back(currentRequest.pHandler);
					currentRequest.hFile = pf::FileIO::FileOpen(currentRequest.hDataBlockHandle->config.strFilename, pf::FileIO::EFileMode_Read);
					CR_ASSERT(currentRequest.hFile != pf::FileIO::cInvalidFileHandle);
					CR_ASSERT(currentRequest.hDataBlockHandle->config.fileSizeInBytes <= currentRequest.hDataBlockHandle->pSlot->memory.size());
					pf::FileIO::FileRead(currentRequest.hFile, &*currentRequest.hDataBlockHandle->pSlot->memory.begin(), currentRequest.hDataBlockHandle->config.fileSizeInBytes);
					currentRequest.state = Request::EState_Register_ReadingFile;
				}
				break;
			}
		case Request::EState_Register_ReadingFile:
			{
				if (!pf::FileIO::FileBusy(currentRequest.hFile))
				{
					currentRequest.state = Request::EState_Register_StartProcessing;
				}
				break;
			}
		case Request::EState_Register_StartProcessing:
			{
				// We have data. Check it's our format and pass on to client
				pCurData = &*currentRequest.hDataBlockHandle->pSlot->memory.begin();
				pCurHeader = (DataBlockHeader*)pCurData;
				CR_ASSERT_MSG(pCurHeader->GetId() == cr::HashedString("DATA"), "File does not contain our data format!");
				pCurHeader++;
				pCurData = (void*)(pCurHeader+1);

				// do we have a processor
				std::map<cr::HashedString, std::set<IDataProcessor*> >::iterator mapIt = dataProcessors.find(pCurHeader->GetId());
				if (mapIt != dataProcessors.end())
				{
					currentRequest.state = Request::EState_Register_Processing;
					itProcessor = mapIt->second.begin();
					itProcessorEnd = mapIt->second.end();
					(*itProcessor)->DataProcessor_Register(pCurHeader, pCurData);
				}
				else
				{
					currentRequest.state = Request::EState_Register_StartRegistering;
				}

				break;
			}
		case Request::EState_Register_Processing:
			{
				if ((*itProcessor)->DataProcessor_AreYouDoneYet())
				{
					if (++itProcessor == itProcessorEnd)
					{
						currentRequest.state = Request::EState_Register_StartRegistering;
					}
					else
					{
						(*itProcessor)->DataProcessor_Register(pCurHeader, pCurData);
					}
				}
				break;
			}
		case Request::EState_Register_StartRegistering:
			{
				// If we have a loadprocessor, call it!
				currentRequest.pHandler->DataRequest_OnRegisterData(currentRequest.hDataBlockHandle->config.hsName, pCurHeader, pCurData);
				pCurHeader = 0;
				pCurData = 0;
				currentRequest.state = Request::EState_Register_Registering;
				break;
			}
		case Request::EState_Register_Registering:
			{
				if (currentRequest.pHandler->DataRequest_AreYouDoneYet(currentRequest.hDataBlockHandle->config.hsName))
				{
					if (currentRequest.hFile != pf::FileIO::cInvalidFileHandle)
					{
						currentRequest.hDataBlockHandle->timeStamp = pf::FileIO::FileGetTimeStamp(currentRequest.hFile);
						pf::FileIO::FileClose(currentRequest.hFile);
						currentRequest.hFile = pf::FileIO::cInvalidFileHandle;
					}

					currentRequest.hDataBlockHandle = 0;
					currentRequest.pHandler = 0;
					currentRequest.state = Request::EState_Idle;
				}

				break;
			}
		case Request::EState_Release_StartUnregistering:
			{
				currentRequest.pHandler->DataRequest_OnUnregisterData(currentRequest.hDataBlockHandle->config.hsName);
				currentRequest.state = Request::EState_Release_Unregistering;
				break;
			}
		case Request::EState_Release_Unregistering:
			{
				if (currentRequest.pHandler->DataRequest_AreYouDoneYet(currentRequest.hDataBlockHandle->config.hsName))
				{
					CR_ASSERT(std::count(currentRequest.hDataBlockHandle->pSlot->handlers.begin(), currentRequest.hDataBlockHandle->pSlot->handlers.end(), currentRequest.pHandler) == 1);
					//std::remove(currentRequest.hDataBlockHandle->pSlot->handlers.begin(), currentRequest.hDataBlockHandle->pSlot->handlers.end(), currentRequest.pHandler);
					for (std::vector<IRequestHandler*>::iterator it = currentRequest.hDataBlockHandle->pSlot->handlers.begin(), end = currentRequest.hDataBlockHandle->pSlot->handlers.end(); it != end; ++it)
					{
						if (*it == currentRequest.pHandler)
						{
							currentRequest.hDataBlockHandle->pSlot->handlers.erase(it);
							break;
						}
					}
					if (currentRequest.hDataBlockHandle->pSlot->handlers.size() == 0)
					{
						currentRequest.state = Request::EState_Release_StartUnprocessing;
					}
					else
					{
						currentRequest.state = Request::EState_Idle;
					}
				}
				break;
			}

		case Request::EState_Release_StartUnprocessing:
			{
				pCurData = &*currentRequest.hDataBlockHandle->pSlot->memory.begin();
				pCurHeader = (DataBlockHeader*)pCurData;
				CR_ASSERT_MSG(pCurHeader->GetId() == cr::HashedString("DATA"), "File does not contain our data format!");
				pCurHeader++;
				pCurData = (void*)(pCurHeader+1);

				// do we have a processor
				std::map<cr::HashedString, std::set<IDataProcessor*> >::iterator mapIt = dataProcessors.find(pCurHeader->GetId());
				if (mapIt != dataProcessors.end())
				{
					currentRequest.state = Request::EState_Release_Unprocessing;
					itProcessor = mapIt->second.begin();
					itProcessorEnd = mapIt->second.end();
					(*itProcessor)->DataProcessor_Unregister(pCurHeader, pCurData);
				}
				else
				{
					currentRequest.state = Request::EState_Release_FreeingSlot;
				}
				break;
			}

		case Request::EState_Release_Unprocessing:
			{
				if ((*itProcessor)->DataProcessor_AreYouDoneYet())
				{
					if (++itProcessor == itProcessorEnd)
					{
						currentRequest.state = Request::EState_Release_FreeingSlot;
					}
					else
					{
						(*itProcessor)->DataProcessor_Unregister(pCurHeader, pCurData);
					}
				}
				break;
			}

		case Request::EState_Release_FreeingSlot:
			{
				CR_ASSERT(currentRequest.hDataBlockHandle->pSlot->handlers.size() == 0);
				currentRequest.hDataBlockHandle->pSlot->pOwner = 0;
				memset(&currentRequest.hDataBlockHandle->pSlot->memory.front(), 0xcd, currentRequest.hDataBlockHandle->pSlot->memory.size());
				currentRequest.hDataBlockHandle->pSlot = 0;
				currentRequest.state = Request::EState_Idle;
				break;
			}
		}

		if (IsIdle() && enableHotload)
		{
			for (std::vector<Slot>::iterator it = slots.begin(), end = slots.end(); it != end; ++it)
			{
				Slot& slot = *it;
				if (slot.pOwner)
				{
					cr::uint64_t lastTimeStamp = slot.pOwner->timeStamp;
					cr::uint64_t currTimeStamp = pf::FileIO::FileGetTimeStamp(it->pOwner->config.strFilename);

					if (currTimeStamp != 0 && currTimeStamp != lastTimeStamp)
					{
						for (std::vector<IRequestHandler*>::iterator itHandler = slot.handlers.begin(), endHandler = slot.handlers.end(); itHandler != endHandler; ++itHandler)
						{
							ReleaseData(slot.pOwner->config.hsName, *itHandler);
						}
						for (std::vector<IRequestHandler*>::iterator itHandler = slot.handlers.begin(), endHandler = slot.handlers.end(); itHandler != endHandler; ++itHandler)
						{
							RequestData(slot.pOwner->config.hsName, *itHandler);
						}
					}
				}
			}
		}

		return !IsIdle();
	}

	void Manager::RegisterDataProcessor(IDataProcessor* pProcessor, cr::HashedString hsDataBlock)
	{
		dataProcessors[hsDataBlock].insert(pProcessor);
	}

	void Manager::UnregisterDataProcessor(IDataProcessor* pProcessor, cr::HashedString hsDataBlock)
	{
		std::set<IDataProcessor*>& processorList = dataProcessors[hsDataBlock];
		std::set<IDataProcessor*>::iterator it = processorList.find(pProcessor);
		if (it != processorList.end())
			processorList.erase(it);
	}
}